package com.fanrui.code.main;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Properties;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.StringUtils;

import com.fanrui.code.chess.ChessGamerManager;
import com.fanrui.code.chess.mahjong.MahjongStandings;
import com.fanrui.code.common.annotation.Config;
import com.fanrui.code.dao.PlayerLogDao;
import com.fanrui.code.dao.TableLogDao;
import com.fanrui.code.family.FamilyDao;
import com.fanrui.code.family.FamilyJobConfig;
import com.fanrui.code.game.SignManager;
import com.fanrui.code.game.TableMainLoop;
import com.fanrui.code.handle.Handle;
import com.fanrui.code.mahjong.util.MahjongCardUtil;
import com.fanrui.code.match.TableConfig;
import com.fanrui.code.message.PlayerSession;
import com.fanrui.code.message.PlayerSessions;
import com.fanrui.code.message.RequestContext;
import com.fanrui.code.player.Player;
import com.fanrui.code.player.PlayerDao;
import com.fanrui.code.player.PlayerManager;
import com.fanrui.code.socket.netty.core.FieldPrepender;
import com.fanrui.code.socket.netty.core.FrameDecoder;
import com.fanrui.code.socket.netty.core.InboundExceptionLogHandler;
import com.fanrui.code.socket.netty.core.OutboundExceptionLogHandler;
import com.fanrui.code.socket.netty.server.SocketServer;
import com.fanrui.code.thread.FrameTask;
import com.fanrui.code.thread.MainLoopPlus.LogExceptionRunnable;
import com.fanrui.code.thread.RobotBreatheThreadPool;
import com.fanrui.code.threadgroup.GroupDeterminer;
import com.fanrui.code.util.CommandsUtil;
import com.fanrui.code.util.core.LoopThreadPool;
import com.fanrui.code.util.core.PropertiesUtil;
import com.fanrui.code.util.core.ThreadManager;
import com.fanrui.code.util.logger.Stdout;
import com.fanrui.proto.cmd.CmdWrapper;
import com.fanrui.proto.cmd.ReqLogout;
import com.fanrui.proto.cmd.ServerCmdWrapper;
import com.google.protobuf.MessageLite;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;

/**
 * @see
 * @author Administrator
 *
 */
public class MainServer {
	
	@Config
	public String protobufCommandServerIP;
	
	@Config
	public Integer protobufCommandServerPort;
	
	@Config
	public Integer mainThreads;
	
	@Config
	public Integer tableThreads;
	
	@Config
	public Integer robotThreads;
	
	@Config
	public String standURL;
	
	/**
	 * http服务器地址
	 */
	@Config
	public String httpServerIp;

	/**
	 * http服务器端口号
	 */
	@Config
	public Integer httpServerPort;
	
	private final static String CONFIGNAME = "fishGame.properties";
	
	protected SocketServer socketServer;
	
	// singleton
	private MainServer() {
		Stdout.GET().info("---------------------服务器启动中---------------------");
		Stdout.GET().info("读取配置文件...");
		if (CONFIGNAME == null || StringUtils.isBlank(CONFIGNAME)) {
			Stdout.GET().error("服务器启动失败， 配置文件名无效！！！");
			return;
		}
		// 设置配置文件
		Properties properties = PropertiesUtil.getPropertiesAddDir(CONFIGNAME);
		if (properties == null) {
			Stdout.GET().error("服务器启动失败， 配置文件文件无效！！");
			return;
		}
		// 初始化配置
		initConfig(properties);
		Stdout.GET().info("配置读取完毕！！！");
		
		// 初始化线程池
		if(null == mainThreads) {
			mainThreads = Runtime.getRuntime().availableProcessors();
			Stdout.GET().info(String.format("mainThreads 配置为null 服务器主线程数被设置成默认值:%d", mainThreads));
		}else {
			Stdout.GET().info(String.format("mainThreads 服务器主线程数被设置成:%d", mainThreads));
		}
		ThreadManager.init(getClass().getSimpleName(), mainThreads);
		if(null == tableThreads) {
			tableThreads = Runtime.getRuntime().availableProcessors();
			Stdout.GET().info(String.format("tableThreads 配置为null 服务器主线程数被设置成默认值:%d", mainThreads));
		}else {
			Stdout.GET().info(String.format("tableThreads 服务器主线程数被设置成:%d", mainThreads));
		}
		LoopThreadPool.init(this.getClass().getSimpleName(), tableThreads);
		if(null == robotThreads) {
			robotThreads = Runtime.getRuntime().availableProcessors();
			Stdout.GET().info(String.format("robotThreads 配置为null 服务器主线程数被设置成默认值:%d", mainThreads));
		}else {
			Stdout.GET().info(String.format("robotThreads 服务器主线程数被设置成:%d", mainThreads));
		}
		RobotBreatheThreadPool.init("RobotBreatheThread", robotThreads);
		
		// 每秒的帧逻辑
		TableMainLoop.getInstance().groupAddFrameLogicTask(new FrameTask(true, 1, 1, TimeUnit.SECONDS) {
			@Override
			protected void run(int spendTime, int waitTime, int detlaTime) {
				GroupDeterminer<Long> group = TableMainLoop.getInstance().getGroupDeterminer();
				ChessGamerManager.getInstance().trick(group, detlaTime);
			}
		});
	}
	
	private static class MainServerSingleton {
		private static MainServer instance = new MainServer();
	}
	
	public static MainServer getInstance() {
		return MainServerSingleton.instance;
	}
	
	/**
	 * 初始化和加载逻辑服务器各种资源
	 */
	public void init() {
		MahjongCardUtil.init();
		// 初始化handle，方便调用
		Handle.getInstance().registe(Handle.class.getPackage().getName());
		// 玩家
		PlayerDao.getInstance().loadAll();
		// 加载匹配场
		TableConfig.getInstance().init();
		FamilyJobConfig.getInstance().init();
		FamilyDao.getInstance().loadAll();
		PlayerDao.getInstance().loadAllPlayerFamily();
		PlayerDao.getInstance().loadAllPlayerClubs();
		SignManager.getInstance().startScheduled();
		MahjongStandings.startSaveStandings();
		PlayerLogDao.getInstance().startSave();
		TableLogDao.getInstance().startSave();
		startProtoBufServer();
		startHttpServer();
	}
	
	/**
	 * 启动Http服务器
	 */
	public void startHttpServer() {
		if (httpServerIp == null || StringUtils.isBlank(httpServerIp)) {
			Stdout.GET().warn("http地址为空， 无法启动http服务器！！！");
			return;
		}
		if (httpServerPort == null || httpServerPort <= 0) {
			Stdout.GET().warn("http端口号为空， 无法启动http服务器！！！");
			return;
		}
		HttpServer server = new HttpServer();
		try {
			ThreadManager.getInstance().execute(new LogExceptionRunnable() {
				@Override
				protected void logExceptionRun() throws Exception {
					server.start(httpServerIp, httpServerPort);
				}
			});
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void startProtoBufServer() {
		if (protobufCommandServerIP == null || StringUtils.isBlank(protobufCommandServerIP)) {
			System.err.println("创建protobuf命令服务器失败 protobufCommandServerIP的值无效 ：" + protobufCommandServerIP);
			return;
		}
		if (protobufCommandServerPort == null || protobufCommandServerPort <= 0) {
			System.err.println("创建protobuf命令服务器失败 protobufCommandServerPort的值无效 ：" + protobufCommandServerPort);
			return;
		}
		try {

			socketServer = new SocketServer(protobufCommandServerIP, protobufCommandServerPort) {
				@Override
				protected ChannelInitializer<SocketChannel> getChannelInitializer() {
					return getChannel();
				}
			};
			ThreadManager.getInstance().execute(new LogExceptionRunnable() {
				@Override
				protected void logExceptionRun() throws Exception {
					socketServer.startUntilClose();
				}
			});
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public ChannelInitializer<SocketChannel> getChannel() {
		return new ChannelInitializer<SocketChannel>() {

			@Override
			protected void initChannel(SocketChannel ch) throws Exception {
				ch.pipeline()
						.addLast("frame", new FrameDecoder()).addLast("field", new FieldPrepender())
						.addLast("decoder", new ProtobufDecoder(ServerCmdWrapper.getDefaultInstance()))
						.addLast("encoder", new ProtobufEncoder())
						.addLast("clientHandler", new SimpleChannelInboundHandler<ServerCmdWrapper>() {

							@Override
							protected void channelRead0(ChannelHandlerContext channel, ServerCmdWrapper msg)
									throws Exception {
								long playerId = msg.getPlayerId();
								CmdWrapper cmdWrapper = msg.getCmdWrapper();
								int requestNumber = cmdWrapper.getCmdNumber();
								MessageLite request = CommandsUtil.unwrapper(cmdWrapper);
								
								TableMainLoop.getInstance().get(playerId).execute(new LogExceptionRunnable() {

									@Override
									protected void logExceptionRun() throws Exception {
										handlerGateServerMessage(channel.channel(), playerId, requestNumber, request);
									}
								 
								 });
								
							}

							@Override
							public void channelActive(ChannelHandlerContext ctx) throws Exception {
								super.channelActive(ctx);
							}

							@Override
							public void channelInactive(ChannelHandlerContext ctx) throws Exception {
								super.channelInactive(ctx);
							}

						}).addLast("inboundException", new InboundExceptionLogHandler())
						.addLast("outboundException", new OutboundExceptionLogHandler());
			}
		};
	}
	
	/**
	 * 接收处理从GateServer来的请求
	 * 
	 * @param channel
	 * @param playerId
	 * @param requestNumber
	 * @param request
	 */
	protected void handlerGateServerMessage(Channel channel, long playerId, int requestNumber, MessageLite request) {
		PlayerSession sender = PlayerSessions.getPlayerSession(playerId);
		if (null == sender) {
			sender = PlayerSessions.newPlayerSession(playerId, channel);
		} else if (!sender.isSameSocket(channel)) {
			// 判断是否为同一个channel，替换掉原来的channel
			sender.changeChannel(channel);
		}
		RequestContext context = new RequestContext(requestNumber, sender);
		dispatchReuqestHandler(context, request);
		if (request instanceof ReqLogout) {
			// 登出移除PlayerSession
			PlayerSessions.removePlayerSession(playerId);
		}
	}
	
	protected void dispatchReuqestHandler(RequestContext context, MessageLite request) {
		// 获取玩家对象
		Player player = PlayerManager.getInstance().findOrCreatePlayer(context.getPlayerId());
		if (player != null) {
			try {
				Handle.getInstance().invokeHandlerMethod(player, context, request);
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 使用反射加载配置
	 * @param properties
	 */
	private void initConfig(Properties properties) {
		Field[] fields = this.getClass().getFields();
		for(Field field : fields) {
			Config config = field.getAnnotation(Config.class);
			if(config != null) {
				try {
					field.setAccessible(true);
					field.set(this, PropertiesUtil.getObjectParameter(properties, field.getName(), field.getType()));
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
	
}
